# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import sys
import os
import json
import math
import subprocess
import urllib.request
import numpy as np
from PIL import Image
from datetime import datetime
from dataclasses import dataclass
from typing import List
from pathlib import Path
from packaging import version
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog,
                             QCheckBox, QComboBox, QSpinBox, QTextEdit, QProgressBar,
                             QMessageBox, QGroupBox, QFormLayout, QGridLayout, QFrame)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
from PyQt5.QtGui import QIcon, QFont
import shutil

sys.dont_write_bytecode = True


@dataclass
class AssetCopyConfig:
    assets_path: str
    target_path: str
    spng_enable: bool
    sjpg_enable: bool
    qoi_enable: bool
    sqoi_enable: bool
    row_enable: bool
    support_format: List[str]
    split_height: int


@dataclass
class PackModelsConfig:
    target_path: str
    main_path: str
    image_file: str
    assets_path: str
    name_length: int


class ConversionThread(QThread):
    """后台转换线程，避免UI卡顿"""
    progress_updated = pyqtSignal(int, str)
    conversion_finished = pyqtSignal(bool, str)

    def __init__(self, config_file, parent=None):
        super().__init__(parent)
        self.config_file = config_file

    def run(self):
        try:
            # 导入主程序中的函数
            from __main__ import copy_assets, pack_assets, AssetCopyConfig, PackModelsConfig

            with open(self.config_file, 'r') as f:
                config_data = json.load(f)

            assets_path = config_data['assets_path']
            image_file = config_data['image_file']
            target_path = os.path.dirname(image_file)
            main_path = config_data['main_path']
            name_length = config_data['name_length']
            split_height = config_data['split_height']
            support_format = [fmt.strip() for fmt in config_data['support_format'].split(',')]

            # 更新进度
            self.progress_updated.emit(10, "正在准备资产复制...")

            copy_config = AssetCopyConfig(
                assets_path=assets_path,
                target_path=target_path,
                spng_enable=config_data['support_spng'],
                sjpg_enable=config_data['support_sjpg'],
                qoi_enable=config_data['support_qoi'],
                sqoi_enable=config_data['support_sqoi'],
                row_enable=config_data['support_raw'],
                support_format=support_format,
                split_height=split_height
            )

            self.progress_updated.emit(30, "开始复制和转换资产...")
            copy_assets(copy_config)

            self.progress_updated.emit(70, "开始打包资产...")
            pack_config = PackModelsConfig(
                target_path=target_path,
                main_path=main_path,
                image_file=image_file,
                assets_path=assets_path,
                name_length=name_length
            )
            pack_assets(pack_config)

            # 验证最终大小
            total_size = os.path.getsize(os.path.join(target_path, image_file))
            recommended_size = math.ceil(total_size / 1024)
            partition_size = math.ceil(int(config_data['assets_size'], 16) / 1024)

            size_warning = ""
            if int(config_data['assets_size'], 16) <= total_size:
                size_warning = f"警告: 给定的资产分区大小({partition_size}K)小于推荐大小({recommended_size}K)"

            self.progress_updated.emit(100, "转换完成!")
            self.conversion_finished.emit(True, size_warning)

        except Exception as e:
            self.progress_updated.emit(100, f"错误: {str(e)}")
            self.conversion_finished.emit(False, str(e))


class MainWindow(QMainWindow):
    """主窗口类"""

    def __init__(self):
        super().__init__()
        self.config_data = {}
        self.init_ui()

    def init_ui(self):
        # 设置窗口标题和大小
        self.setWindowTitle("图像资产转换工具")
        self.setGeometry(100, 100, 900, 700)

        # 创建中心部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 创建标签页
        self.tabs = QTabWidget()

        # 配置标签页
        self.config_tab = QWidget()
        self.config_layout = QVBoxLayout(self.config_tab)
        self.setup_config_tab()

        # 转换标签页
        self.convert_tab = QWidget()
        self.convert_layout = QVBoxLayout(self.convert_tab)
        self.setup_convert_tab()

        # 日志标签页
        self.log_tab = QWidget()
        self.log_layout = QVBoxLayout(self.log_tab)
        self.setup_log_tab()

        # 添加标签页到标签控件
        self.tabs.addTab(self.config_tab, "配置")
        self.tabs.addTab(self.convert_tab, "转换")
        self.tabs.addTab(self.log_tab, "日志")

        main_layout.addWidget(self.tabs)

        # 状态栏
        self.statusBar().showMessage("就绪")

    def setup_config_tab(self):
        """设置配置标签页"""
        # 创建配置文件选择组
        config_file_group = QGroupBox("配置文件")
        config_file_layout = QHBoxLayout()

        self.config_file_path = QLineEdit()
        self.config_file_path.setReadOnly(True)

        select_config_btn = QPushButton("选择配置文件")
        select_config_btn.clicked.connect(self.select_config_file)

        create_config_btn = QPushButton("创建配置文件")
        create_config_btn.clicked.connect(self.create_config_file)

        config_file_layout.addWidget(QLabel("配置文件路径:"))
        config_file_layout.addWidget(self.config_file_path)
        config_file_layout.addWidget(select_config_btn)
        config_file_layout.addWidget(create_config_btn)
        config_file_group.setLayout(config_file_layout)

        # 创建配置参数组
        self.config_params_group = QGroupBox("配置参数")
        self.config_params_layout = QFormLayout()

        # 基本路径设置
        self.assets_path_edit = QLineEdit()
        self.target_path_edit = QLineEdit()
        self.image_file_edit = QLineEdit()
        self.main_path_edit = QLineEdit()

        browse_assets_btn = QPushButton("浏览...")
        browse_assets_btn.clicked.connect(lambda: self.browse_directory(self.assets_path_edit, "选择资源目录"))

        browse_target_btn = QPushButton("浏览...")
        browse_target_btn.clicked.connect(lambda: self.browse_directory(self.target_path_edit, "选择目标目录"))

        browse_image_btn = QPushButton("浏览...")
        browse_image_btn.clicked.connect(
            lambda: self.browse_file(self.image_file_edit, "选择输出文件", "二进制文件 (*.bin)"))

        browse_main_btn = QPushButton("浏览...")
        browse_main_btn.clicked.connect(lambda: self.browse_directory(self.main_path_edit, "选择主目录"))

        path_layout1 = QHBoxLayout()
        path_layout1.addWidget(self.assets_path_edit)
        path_layout1.addWidget(browse_assets_btn)

        path_layout2 = QHBoxLayout()
        path_layout2.addWidget(self.target_path_edit)
        path_layout2.addWidget(browse_target_btn)

        path_layout3 = QHBoxLayout()
        path_layout3.addWidget(self.image_file_edit)
        path_layout3.addWidget(browse_image_btn)

        path_layout4 = QHBoxLayout()
        path_layout4.addWidget(self.main_path_edit)
        path_layout4.addWidget(browse_main_btn)

        self.config_params_layout.addRow("资源目录:", path_layout1)
        self.config_params_layout.addRow("目标目录:", path_layout2)
        self.config_params_layout.addRow("输出文件:", path_layout3)
        self.config_params_layout.addRow("主目录:", path_layout4)

        # 格式支持设置
        format_frame = QFrame()
        format_frame.setFrameShape(QFrame.StyledPanel)
        format_layout = QGridLayout(format_frame)

        self.support_jpg_check = QCheckBox("JPG")
        self.support_png_check = QCheckBox("PNG")
        self.support_spng_check = QCheckBox("SPNG")
        self.support_sjpg_check = QCheckBox("SJPG")
        self.support_qoi_check = QCheckBox("QOI")
        self.support_sqoi_check = QCheckBox("SQOI")
        self.support_raw_check = QCheckBox("RAW")

        format_layout.addWidget(QLabel("支持的格式:"), 0, 0)
        format_layout.addWidget(self.support_jpg_check, 0, 1)
        format_layout.addWidget(self.support_png_check, 0, 2)

        format_layout.addWidget(QLabel("转换选项:"), 1, 0)
        format_layout.addWidget(self.support_spng_check, 1, 1)
        format_layout.addWidget(self.support_sjpg_check, 1, 2)
        format_layout.addWidget(self.support_qoi_check, 1, 3)
        format_layout.addWidget(self.support_sqoi_check, 1, 4)
        format_layout.addWidget(self.support_raw_check, 1, 5)

        self.config_params_layout.addRow(format_frame)

        # 其他参数
        self.split_height_spin = QSpinBox()
        self.split_height_spin.setRange(0, 10000)
        self.split_height_spin.setValue(int(self.config_data.get('split_height', 200)))

        self.name_length_spin = QSpinBox()
        self.name_length_spin.setRange(10, 100)
        self.name_length_spin.setValue(int(self.config_data.get('name_length', 32)))

        self.assets_size_edit = QLineEdit()
        self.assets_size_edit.setText("0x8000")

        self.lvgl_ver_combo = QComboBox()
        self.lvgl_ver_combo.addItems(["8.3.7", "9.0.0", "9.1.0", "9.2.0"])

        self.config_params_layout.addRow("分块高度:", self.split_height_spin)
        self.config_params_layout.addRow("最大文件名长度:", self.name_length_spin)
        self.config_params_layout.addRow("资产分区大小:", self.assets_size_edit)
        self.config_params_layout.addRow("LVGL版本:", self.lvgl_ver_combo)

        self.config_params_group.setLayout(self.config_params_layout)
        self.config_params_group.setEnabled(False)

        # 保存配置按钮
        save_config_btn = QPushButton("保存配置")
        save_config_btn.setIcon(QIcon.fromTheme("document-save"))
        save_config_btn.clicked.connect(self.save_config)

        # 添加到布局
        self.config_layout.addWidget(config_file_group)
        self.config_layout.addWidget(self.config_params_group)
        self.config_layout.addWidget(save_config_btn)
        self.config_layout.addStretch()

    def setup_convert_tab(self):
        """设置转换标签页"""
        # 创建转换控制组
        convert_group = QGroupBox("转换控制")
        convert_layout = QVBoxLayout(convert_group)

        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)

        # 状态标签
        self.status_label = QLabel("等待开始转换...")
        self.status_label.setAlignment(Qt.AlignCenter)
        self.status_label.setStyleSheet("font-weight: bold;")

        # 转换按钮
        convert_btn = QPushButton("开始转换")
        convert_btn.setIcon(QIcon.fromTheme("media-playback-start"))
        convert_btn.setMinimumHeight(40)
        convert_btn.clicked.connect(self.start_conversion)

        # 添加到布局
        convert_layout.addWidget(QLabel("转换进度:"))
        convert_layout.addWidget(self.progress_bar)
        convert_layout.addWidget(self.status_label)
        convert_layout.addWidget(convert_btn)

        # 结果信息组
        self.result_group = QGroupBox("转换结果")
        self.result_layout = QFormLayout(self.result_group)

        self.total_files_label = QLabel("")
        self.total_size_label = QLabel("")
        self.checksum_label = QLabel("")
        self.recommended_size_label = QLabel("")

        self.result_layout.addRow("文件总数:", self.total_files_label)
        self.result_layout.addRow("总大小:", self.total_size_label)
        self.result_layout.addRow("校验和:", self.checksum_label)
        self.result_layout.addRow("推荐分区大小:", self.recommended_size_label)

        self.result_group.hide()

        # 添加到主布局
        self.convert_layout.addWidget(convert_group)
        self.convert_layout.addWidget(self.result_group)
        self.convert_layout.addStretch()

    def setup_log_tab(self):
        """设置日志标签页"""
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setLineWrapMode(QTextEdit.NoWrap)
        self.log_text.setFont(QFont("Consolas", 10))

        clear_log_btn = QPushButton("清空日志")
        clear_log_btn.clicked.connect(self.clear_log)

        self.log_layout.addWidget(self.log_text)
        self.log_layout.addWidget(clear_log_btn)

    def select_config_file(self):
        """选择配置文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择配置文件", "", "JSON文件 (*.json);;所有文件 (*)"
        )

        if file_path:
            self.config_file_path.setText(file_path)
            self.load_config(file_path)

    def load_config(self, file_path):
        """加载配置文件"""
        try:
            with open(file_path, 'r') as f:
                self.config_data = json.load(f)

            # 填充UI字段
            self.assets_path_edit.setText(self.config_data.get('assets_path', ''))
            self.target_path_edit.setText(os.path.dirname(self.config_data.get('image_file', '')))
            self.image_file_edit.setText(self.config_data.get('image_file', ''))
            self.main_path_edit.setText(self.config_data.get('main_path', ''))

            # 格式支持
            support_format = self.config_data.get('support_format', 'jpg, png').split(',')
            self.support_jpg_check.setChecked('jpg' in [fmt.strip().lower() for fmt in support_format])
            self.support_png_check.setChecked('png' in [fmt.strip().lower() for fmt in support_format])

            # 转换选项
            self.support_spng_check.setChecked(self.config_data.get('support_spng', False))
            self.support_sjpg_check.setChecked(self.config_data.get('support_sjpg', False))
            self.support_qoi_check.setChecked(self.config_data.get('support_qoi', False))
            self.support_sqoi_check.setChecked(self.config_data.get('support_sqoi', False))
            self.support_raw_check.setChecked(self.config_data.get('support_raw', False))

            # 其他参数
            self.split_height_spin.setValue(self.config_data.get('split_height', 200))
            self.name_length_spin.setValue(self.config_data.get('name_length', 32))
            self.assets_size_edit.setText(self.config_data.get('assets_size', '0x8000'))
            self.lvgl_ver_combo.setCurrentText(self.config_data.get('lvgl_ver', '9.0.0'))

            self.config_params_group.setEnabled(True)
            self.log(f"已加载配置文件: {file_path}")
            self.statusBar().showMessage(f"已加载配置文件: {os.path.basename(file_path)}")

        except Exception as e:
            QMessageBox.critical(self, "错误", f"加载配置文件失败: {str(e)}")
            self.log(f"错误: 加载配置文件失败 - {str(e)}")

    def create_config_file(self):
        """创建新的配置文件"""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "创建配置文件", "", "JSON文件 (*.json);;所有文件 (*)"
        )

        if file_path:
            # 创建默认配置
            default_config = {
                "assets_path": "",
                "image_file": "",
                "main_path": "",
                "lvgl_ver": "9.0.0",
                "support_format": "jpg, png",
                "support_spng": True,
                "support_sjpg": True,
                "support_qoi": True,
                "support_sqoi": True,
                "support_raw": True,
                "split_height": 200,
                "name_length": 32,
                "assets_size": "0x8000"
            }

            try:
                with open(file_path, 'w') as f:
                    json.dump(default_config, f, indent=4)

                self.config_file_path.setText(file_path)
                self.load_config(file_path)
                self.log(f"已创建新配置文件: {file_path}")

            except Exception as e:
                QMessageBox.critical(self, "错误", f"创建配置文件失败: {str(e)}")
                self.log(f"错误: 创建配置文件失败 - {str(e)}")

    def save_config(self):
        """保存配置文件"""
        if not self.config_file_path.text():
            QMessageBox.warning(self, "警告", "请先选择或创建配置文件")
            return

        try:
            # 更新配置数据
            self.config_data['assets_path'] = self.assets_path_edit.text()
            self.config_data['image_file'] = self.image_file_edit.text()
            self.config_data['main_path'] = self.main_path_edit.getText()

            # 格式支持
            format_list = []
            if self.support_jpg_check.isChecked():
                format_list.append('jpg')
            if self.support_png_check.isChecked():
                format_list.append('png')
            self.config_data['support_format'] = ', '.join(format_list)

            # 转换选项
            self.config_data['support_spng'] = self.support_spng_check.isChecked()
            self.config_data['support_sjpg'] = self.support_sjpg_check.isChecked()
            self.config_data['support_qoi'] = self.support_qoi_check.isChecked()
            self.config_data['support_sqoi'] = self.support_sqoi_check.isChecked()
            self.config_data['support_raw'] = self.support_raw_check.isChecked()

            # 其他参数
            self.config_data['split_height'] = self.split_height_spin.getValue()
            self.config_data['name_length'] = self.name_length_spin.getValue()
            self.config_data['assets_size'] = self.assets_size_edit.getText()
            self.config_data['lvgl_ver'] = self.lvgl_ver_combo.currentText()

            # 保存到文件
            with open(self.config_file_path.text(), 'w') as f:
                json.dump(self.config_data, f, indent=4)

            QMessageBox.information(self, "成功", "配置已保存")
            self.log(f"已保存配置文件: {self.config_file_path.text()}")

        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存配置文件失败: {str(e)}")
            self.log(f"错误: 保存配置文件失败 - {str(e)}")

    def browse_directory(self, target_edit, caption):
        """浏览目录并设置到目标编辑框"""
        dir_path = QFileDialog.getExistingDirectory(self, caption)
        if dir_path:
            target_edit.setText(dir_path)

    def browse_file(self, target_edit, caption, filter):
        """浏览文件并设置到目标编辑框"""
        file_path, _ = QFileDialog.getOpenFileName(self, caption, "", filter)
        if file_path:
            target_edit.setText(file_path)

    def start_conversion(self):
        """开始转换过程"""
        if not self.config_file_path.text():
            QMessageBox.warning(self, "警告", "请先选择配置文件")
            return

        # 检查配置文件是否存在
        if not os.path.exists(self.config_file_path.text()):
            QMessageBox.warning(self, "警告", f"配置文件不存在: {self.config_file_path.text()}")
            return

        # 重置结果信息
        self.result_group.hide()
        self.total_files_label.setText("")
        self.total_size_label.setText("")
        self.checksum_label.setText("")
        self.recommended_size_label.setText("")

        # 清空日志
        self.clear_log()

        # 启动转换线程
        self.conversion_thread = ConversionThread(self.config_file_path.text())
        self.conversion_thread.progress_updated.connect(self.update_progress)
        self.conversion_thread.conversion_finished.connect(self.conversion_completed)
        self.conversion_thread.start()

        self.log("开始转换过程...")
        self.statusBar().showMessage("正在转换...")

    def update_progress(self, value, message):
        """更新进度条和状态信息"""
        self.progress_bar.setValue(value)
        self.status_label.setText(message)
        self.log(message)

    def conversion_completed(self, success, message):
        """转换完成回调"""
        if success:
            self.statusBar().showMessage("转换完成")
            QMessageBox.information(self, "成功", "转换过程已完成")

            # 显示结果信息
            try:
                target_path = os.path.dirname(self.config_data.get('image_file', ''))
                image_file = self.config_data.get('image_file', '')
                header_file = os.path.join(
                    self.config_data.get('main_path', ''),
                    f"mmap_generate_{os.path.basename(self.config_data.get('assets_path', ''))}.h"
                )

                # 读取头部文件获取信息
                if os.path.exists(header_file):
                    with open(header_file, 'r') as f:
                        header_content = f.read()

                        # 提取文件数
                        import re
                        files_match = re.search(r'#define MMAP_.*?_FILES\s+(\d+)', header_content)
                        checksum_match = re.search(r'#define MMAP_.*?_CHECKSUM\s+0x(\w+)', header_content)

                        if files_match:
                            self.total_files_label.setText(files_match.group(1))

                        if checksum_match:
                            self.checksum_label.setText(f"0x{checksum_match.group(1)}")

                # 获取文件大小
                if os.path.exists(image_file):
                    total_size = os.path.getsize(image_file)
                    self.total_size_label.setText(f"{total_size} 字节 ({total_size / 1024:.2f} KB)")

                    # 计算推荐大小
                    recommended_size = math.ceil(total_size / 1024)
                    self.recommended_size_label.setText(f"{recommended_size} KB")

                self.result_group.show()

            except Exception as e:
                self.log(f"错误: 读取结果信息失败 - {str(e)}")

        else:
            self.statusBar().showMessage("转换失败")
            QMessageBox.critical(self, "错误", f"转换过程失败: {message}")

    def log(self, message):
        """添加日志信息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.append(f"[{timestamp}] {message}")

    def clear_log(self):
        """清空日志"""
        self.log_text.clear()


# 下面是原脚本中的核心函数，保持不变
def generate_header_filename(path):
    asset_name = os.path.basename(path)
    header_filename = f'mmap_generate_{asset_name}.h'
    return header_filename


def compute_checksum(data):
    checksum = sum(data) & 0xFFFF
    return checksum


def sort_key(filename):
    basename, extension = os.path.splitext(filename)
    return extension, basename


def download_v8_script(convert_path):
    """
    Ensure that the lvgl_image_converter repository is present at the specified path.
    If not, clone the repository. Then, checkout to a specific commit.

    Parameters:
    - convert_path (str): The directory path where lvgl_image_converter should be located.
    """

    # Check if convert_path is not empty
    if convert_path:
        # If the directory does not exist, create it and clone the repository
        if not os.path.exists(convert_path):
            os.makedirs(convert_path, exist_ok=True)
            try:
                subprocess.run(
                    ['git', 'clone', 'https://github.com/W-Mai/lvgl_image_converter.git', convert_path],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    check=True
                )
            except subprocess.CalledProcessError as e:
                print(f'Git clone failed: {e}')
                sys.exit(1)

            # Checkout to the specific commit
            try:
                subprocess.run(
                    ['git', 'checkout', '9174634e9dcc1b21a63668969406897aad650f35'],
                    cwd=convert_path,
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    check=True
                )
            except subprocess.CalledProcessError as e:
                print(f'Failed to checkout to the specific commit: {e}')
                sys.exit(1)
    else:
        print('Error: convert_path is NULL')
        sys.exit(1)


def download_v9_script(url: str, destination: str) -> None:
    """
    Download a Python script from a URL to a local destination.

    Parameters:
    - url (str): URL to download the script from.
    - destination (str): Local path to save the downloaded script.

    Raises:
    - Exception: If the download fails.
    """
    file_path = Path(destination)

    # Check if the file already exists
    if file_path.exists():
        if file_path.is_file():
            return

    try:
        # Create the parent directories if they do not exist
        file_path.parent.mkdir(parents=True, exist_ok=True)

        # Open the URL and retrieve the data
        with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file:
            data = response.read()  # Read the entire response
            out_file.write(data)  # Write data to the local file

    except urllib.error.HTTPError as e:
        print(f'HTTP Error: {e.code} - {e.reason} when accessing {url}')
        sys.exit(1)
    except urllib.error.URLError as e:
        print(f'URL Error: {e.reason} when accessing {url}')
        sys.exit(1)
    except Exception as e:
        print(f'An unexpected error occurred: {e}')
        sys.exit(1)


def split_image(im, block_size, input_dir, ext, convert_to_qoi):
    """Splits the image into blocks based on the block size."""
    width, height = im.size

    if block_size:
        splits = math.ceil(height / block_size)
    else:
        splits = 1

    for i in range(splits):
        if i < splits - 1:
            crop = im.crop((0, i * block_size, width, (i + 1) * block_size))
        else:
            crop = im.crop((0, i * block_size, width, height))

        output_path = os.path.join(input_dir, str(i) + ext)
        crop.save(output_path, quality=100)

        if convert_to_qoi:
            try:
                # 动态导入qoi模块
                import importlib
                qoi_module = importlib.import_module('qoi-conv.qoi')
                Qoi = qoi_module.Qoi
                replace_extension = qoi_module.replace_extension

                with Image.open(output_path) as img:
                    if img.mode != 'RGBA':
                        img = img.convert('RGBA')

                    img_data = np.asarray(img)
                    out_path = qoi_module.replace_extension(output_path, 'qoi')
                    new_image = qoi_module.Qoi().save(out_path, img_data)
                    os.remove(output_path)
            except Exception as e:
                print(f"Error converting to QOI: {e}")
                # 如果转换失败，继续处理

    return width, height, splits


def create_header(width, height, splits, split_height, lenbuf, ext):
    """Creates the header for the output file based on the format."""
    header = bytearray()

    if ext.lower() == '.jpg':
        header += bytearray('_SJPG__'.encode('UTF-8'))
    elif ext.lower() == '.png':
        header += bytearray('_SPNG__'.encode('UTF-8'))
    elif ext.lower() == '.qoi':
        header += bytearray('_SQOI__'.encode('UTF-8'))

    # 6 BYTES VERSION
    header += bytearray(('\x00V1.00\x00').encode('UTF-8'))

    # WIDTH 2 BYTES
    header += width.to_bytes(2, byteorder='little')

    # HEIGHT 2 BYTES
    header += height.to_bytes(2, byteorder='little')

    # NUMBER OF ITEMS 2 BYTES
    header += splits.to_bytes(2, byteorder='little')

    # SPLIT HEIGHT 2 BYTES
    header += split_height.to_bytes(2, byteorder='little')

    for item_len in lenbuf:
        # LENGTH 2 BYTES
        header += item_len.to_bytes(2, byteorder='little')

    return header


def save_image(output_file_path, header, split_data):
    """Saves the image with the constructed header and split data."""
    with open(output_file_path, 'wb') as f:
        if header is not None:
            f.write(header + split_data)
        else:
            f.write(split_data)


def handle_lvgl_version_v9(input_file: str, input_dir: str,
                           input_filename: str, convert_path: str) -> None:
    """
    Handle conversion for LVGL versions greater than 9.0.

    Parameters:
    - input_file (str): Path to the input image file.
    - input_dir (str): Directory of the input file.
    - input_filename (str): Name of the input file.
    - convert_path (str): Path for conversion scripts and outputs.
    """

    convert_file = os.path.join(convert_path, 'LVGLImage.py')
    lvgl_image_url = 'https://raw.githubusercontent.com/lvgl/lvgl/master/scripts/LVGLImage.py'

    download_v9_script(url=lvgl_image_url, destination=convert_file)
    lvgl_script = Path(convert_file)

    cmd = [
        'python',
        str(lvgl_script),
        '--ofmt', 'BIN',
        '--cf', config_data['support_raw_cf'],
        '--compress', 'NONE',
        '--output', str(input_dir),
        input_file
    ]

    try:
        result = subprocess.run(
            cmd,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        print(f'Completed {input_filename} -> BIN')
    except subprocess.CalledProcessError as e:
        print('An error occurred while executing LVGLImage.py:')
        print(e.stderr)
        sys.exit(e.returncode)


def handle_lvgl_version_v8(input_file: str, input_dir: str, input_filename: str, convert_path: str) -> None:
    """
    Handle conversion for supported LVGL versions (<= 9.0).

    Parameters:
    - input_file (str): Path to the input image file.
    - input_dir (str): Directory of the input file.
    - input_filename (str): Name of the input file.
    - convert_path (str): Path for conversion scripts and outputs.
    """

    download_v8_script(convert_path=convert_path)

    if convert_path not in sys.path:
        sys.path.append(convert_path)

    try:
        import lv_img_conv
    except ImportError as e:
        print(f"Failed to import 'lv_img_conv' from '{convert_path}': {e}")
        sys.exit(1)

    try:
        lv_img_conv.conv_one_file(
            root=Path(input_dir),
            filepath=Path(input_file),
            f=config_data['support_raw_ff'],
            cf=config_data['support_raw_cf'],
            ff='BIN',
            dither=config_data['support_raw_dither'],
            bgr_mode=config_data['support_raw_bgr'],
        )
        print(f'Completed {input_filename} -> BIN')
    except KeyError as e:
        print(f'Missing configuration key: {e}')
        sys.exit(1)
    except Exception as e:
        print(f'An error occurred during conversion: {e}')
        sys.exit(1)


def process_image(input_file, height_str, output_extension, convert_to_qoi=False):
    """Main function to process the image and save it as .sjpg, .spng, or .sqoi."""
    try:
        SPLIT_HEIGHT = int(height_str)
        if SPLIT_HEIGHT < 0:
            raise ValueError('Height must be a positive integer')
    except ValueError as e:
        print('Error: Height must be a positive integer')
        sys.exit(1)

    input_dir, input_filename = os.path.split(input_file)
    base_filename, ext = os.path.splitext(input_filename)
    OUTPUT_FILE_NAME = base_filename

    try:
        im = Image.open(input_file)
    except Exception as e:
        print('Error:', e)
        sys.exit(0)

    width, height, splits = split_image(im, SPLIT_HEIGHT, input_dir, ext, convert_to_qoi)

    split_data = bytearray()
    lenbuf = []

    if convert_to_qoi:
        ext = '.qoi'

    for i in range(splits):
        with open(os.path.join(input_dir, str(i) + ext), 'rb') as f:
            a = f.read()
        split_data += a
        lenbuf.append(len(a))
        os.remove(os.path.join(input_dir, str(i) + ext))

    header = None
    if splits == 1 and convert_to_qoi:
        output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + ext)
    else:
        header = create_header(width, height, splits, SPLIT_HEIGHT, lenbuf, ext)
        output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + output_extension)

    save_image(output_file_path, header, split_data)

    print('Completed', input_filename, '->', os.path.basename(output_file_path))


def convert_image_to_qoi(input_file, height_str):
    process_image(input_file, height_str, '.sqoi', convert_to_qoi=True)


def convert_image_to_simg(input_file, height_str):
    input_dir, input_filename = os.path.split(input_file)
    _, ext = os.path.splitext(input_filename)
    output_extension = '.sjpg' if ext.lower() == '.jpg' else '.spng'
    process_image(input_file, height_str, output_extension, convert_to_qoi=False)


def convert_image_to_raw(input_file: str) -> None:
    """
    Convert an image to raw binary format compatible with LVGL.

    Parameters:
    - input_file (str): Path to the input image file.

    Raises:
    - FileNotFoundError: If required scripts are not found.
    - subprocess.CalledProcessError: If the external conversion script fails.
    - KeyError: If required keys are missing in config_data.
    """
    input_dir, input_filename = os.path.split(input_file)
    _, ext = os.path.splitext(input_filename)
    convert_path = os.path.join(os.path.dirname(input_file), 'lvgl_image_converter')
    lvgl_ver_str = config_data.get('lvgl_ver', '9.0.0')

    try:
        lvgl_version = version.parse(lvgl_ver_str)
    except version.InvalidVersion:
        print(f'Invalid LVGL version format: {lvgl_ver_str}')
        sys.exit(1)

    if lvgl_version >= version.parse('9.0.0'):
        handle_lvgl_version_v9(
            input_file=input_file,
            input_dir=input_dir,
            input_filename=input_filename,
            convert_path=convert_path
        )
    else:
        handle_lvgl_version_v8(
            input_file=input_file,
            input_dir=input_dir,
            input_filename=input_filename,
            convert_path=convert_path
        )


def pack_assets(config: PackModelsConfig):
    """
    Pack models based on the provided configuration.
    """

    target_path = config.target_path
    assets_c_path = config.main_path
    out_file = config.image_file
    assets_path = config.assets_path
    max_name_len = config.name_length
    print(f'tarmy:target_path = {target_path},assets_c_path = {assets_c_path}, out_file = {out_file}')
    merged_data = bytearray()
    file_info_list = []
    skip_files = ['config.json', 'lvgl_image_converter']

    file_list = sorted(os.listdir(target_path), key=sort_key)
    for filename in file_list:
        if filename in skip_files:
            continue

        file_path = os.path.join(target_path, filename)
        file_name = os.path.basename(file_path)
        file_size = os.path.getsize(file_path)

        try:
            img = Image.open(file_path)
            width, height = img.size
        except Exception as e:
            # print("Error:", e)
            _, file_extension = os.path.splitext(file_path)
            if file_extension.lower() in ['.sjpg', '.spng', '.sqoi']:
                offset = 14
                with open(file_path, 'rb') as f:
                    f.seek(offset)
                    width_bytes = f.read(2)
                    height_bytes = f.read(2)
                    width = int.from_bytes(width_bytes, byteorder='little')
                    height = int.from_bytes(height_bytes, byteorder='little')
            else:
                width, height = 0, 0

        file_info_list.append((file_name, len(merged_data), file_size, width, height))
        # Add 0x5A5A prefix to merged_data
        merged_data.extend(b'\x5A' * 2)

        with open(file_path, 'rb') as bin_file:
            bin_data = bin_file.read()

        merged_data.extend(bin_data)

    total_files = len(file_info_list)

    mmap_table = bytearray()
    for file_name, offset, file_size, width, height in file_info_list:
        if len(file_name) > int(max_name_len):
            print(f'\033[1;33mWarn:\033[0m "{file_name}" exceeds {max_name_len} bytes and will be truncated.')
        fixed_name = file_name.ljust(int(max_name_len), '\0')[:int(max_name_len)]
        mmap_table.extend(fixed_name.encode('utf-8'))
        mmap_table.extend(file_size.to_bytes(4, byteorder='little'))
        mmap_table.extend(offset.to_bytes(4, byteorder='little'))
        mmap_table.extend(width.to_bytes(2, byteorder='little'))
        mmap_table.extend(height.to_bytes(2, byteorder='little'))

    combined_data = mmap_table + merged_data
    combined_checksum = compute_checksum(combined_data)
    combined_data_length = len(combined_data).to_bytes(4, byteorder='little')
    header_data = total_files.to_bytes(4, byteorder='little') + combined_checksum.to_bytes(4, byteorder='little')
    final_data = header_data + combined_data_length + combined_data

    with open(out_file, 'wb') as output_bin:
        output_bin.write(final_data)

    os.makedirs(assets_c_path, exist_ok=True)
    current_year = datetime.now().year

    asset_name = os.path.basename(assets_path)
    file_path = os.path.join(assets_c_path, f'mmap_generate_{asset_name}.h')
    with open(file_path, 'w') as output_header:
        output_header.write('/*\n')
        output_header.write(
            ' * SPDX-FileCopyrightText: 2022-{} Espressif Systems (Shanghai) CO LTD\n'.format(current_year))
        output_header.write(' *\n')
        output_header.write(' * SPDX-License-Identifier: Apache-2.0\n')
        output_header.write(' */\n\n')
        output_header.write('/**\n')
        output_header.write(' * @file\n')
        output_header.write(" * @brief This file was generated by esp_mmap_assets, don't modify it\n")
        output_header.write(' */\n\n')
        output_header.write('#pragma once\n\n')
        output_header.write("#include \"esp_mmap_assets.h\"\n\n")
        output_header.write(f'#define MMAP_{asset_name.upper()}_FILES           {total_files}\n')
        output_header.write(f'#define MMAP_{asset_name.upper()}_CHECKSUM        0x{combined_checksum:04X}\n\n')
        output_header.write(f'enum MMAP_{asset_name.upper()}_LISTS {{\n')

        for i, (file_name, _, _, _, _) in enumerate(file_info_list):
            enum_name = file_name.replace('.', '_')
            output_header.write(
                f'    MMAP_{asset_name.upper()}_{enum_name.upper()} = {i},        /*!< {file_name} */\n')

        output_header.write('};\n')

    print(f'All bin files have been merged into {out_file}')


def copy_assets(config: AssetCopyConfig):
    """
    Copy assets to target_path based on the provided configuration.
    """
    format_tuple = tuple(config.support_format)
    assets_path = config.assets_path
    target_path = config.target_path

    for filename in os.listdir(assets_path):
        if any(filename.endswith(suffix) for suffix in format_tuple):
            source_file = os.path.join(assets_path, filename)
            target_file = os.path.join(target_path, filename)
            print(f'tarmy:before copy target_file =  {target_file}')
            shutil.copyfile(source_file, target_file)
            # 检查文件是否存在
            if os.path.exists(target_file):
                print(f'File copied successfully to {target_file}')
            print(f'tarmy:target_file =  {target_file}')
            conversion_map = {
                '.jpg': [
                    (config.sjpg_enable, convert_image_to_simg),
                    (config.qoi_enable, convert_image_to_qoi),
                ],
                '.png': [
                    (config.spng_enable, convert_image_to_simg),
                    (config.qoi_enable, convert_image_to_qoi),
                ],
            }

            file_ext = os.path.splitext(filename)[1].lower()
            conversions = conversion_map.get(file_ext, [])
            converted = False

            for enable_flag, convert_func in conversions:
                if enable_flag:
                    convert_func(target_file, config.split_height)
                    converted = True
                    break

            if not converted and config.row_enable:
                convert_image_to_raw(target_file)
                os.remove(target_file)
        else:
            print(f'No match found for file: {filename}, format_tuple: {format_tuple}')


if __name__ == '__main__':
    # 确保qoi模块在路径中
    try:
        import qoi
    except ImportError:
        print("警告: 未找到qoi模块，QOI转换功能可能不可用")

    # 启动GUI应用
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())    